0523. 连续的子数组和【中等】
1. 📝 题目描述
给你一个整数数组 nums 和一个整数 k,如果 nums 有一个好的子数组返回 true,否则返回 false:
一个好的子数组是:
- 长度至少为 2,且
- 子数组元素总和为
k的倍数。
注意:
- 子数组是数组中连续的部分。
- 如果存在一个整数
n,令整数x符合x = n * k,则称x是k的一个倍数。0始终 视为k的一个倍数。
示例 1:
txt
输入:nums = [23,2,4,6,7], k = 6
输出:true
解释:[2,4] 是一个大小为 2 的子数组,并且和为 6。1
2
3
2
3
示例 2:
txt
输入:nums = [23,2,6,4,7], k = 6
输出:true
解释:[23, 2, 6, 4, 7] 是大小为 5 的子数组,并且和为 42。
42 是 6 的倍数,因为 42 = 7 * 6 且 7 是一个整数。1
2
3
4
2
3
4
示例 3:
txt
输入:nums = [23,2,6,4,7], k = 13
输出:false1
2
2
提示:
1 <= nums.length <= 10^50 <= nums[i] <= 10^90 <= sum(nums[i]) <= 2^31 - 11 <= k <= 2^31 - 1
2. 🎯 s.1 - 前缀和 + 哈希表
js
/**
* @param {number[]} nums
* @param {number} k
* @return {boolean}
*/
var checkSubarraySum = function (nums, k) {
const n = nums.length
if (n < 2) return false
const map = new Map()
// 初始化,前缀和余数为 0 的下标为 -1,为了处理从下标 0 开始的子数组
map.set(0, -1)
let prefixSum = 0
for (let i = 0; i < n; i++) {
prefixSum += nums[i]
// 计算余数,同余定理:
// (sum[i] - sum[j]) % k == 0 => sum[i] % k == sum[j] % k
const remainder = prefixSum % k
// 如果余数已经出现过
if (map.has(remainder)) {
const prevIndex = map.get(remainder)
// 题目要求子数组长度至少为 2
if (i - prevIndex >= 2) {
return true
}
} else {
// 只有当余数不存在时才存入,因为我们需要保持这个余数对应的下标尽可能小
// 这样 i - prevIndex 才更容易 >= 2
map.set(remainder, i)
}
}
return false
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
- 时间复杂度:
,其中 N 是数组 nums 的长度 - 空间复杂度:
,哈希表最多存储 个不同的余数
算法思路:
题目要求判断是否存在一个长度至少为 2 的连续子数组,其元素和为 k 的倍数,我们可以暴力枚举所有可能的子数组来判定,但更好的做法是通过 同余定理 来优化判定流程,这样可以避免枚举所有子数组。
核心公式 => 同余定理:
在本题中,如果前缀和同余:
实现步骤:
- 维护一个哈希表
map,记录prefixSum % k的余数及其第一次出现的下标 - 初始化
map.set(0, -1)- 将前缀和为 0 的索引设为 -1 的目的是为了处理从下标 0 开始的子数组
- 即
的情况
- 遍历数组,计算当前的前缀和
prefixSum和余数remainder- 如果
remainder已经在map中存在:- 取出之前记录的下标
prevIndex - 如果当前下标
i满足i - prevIndex >= 2,则说明找到了符合条件的子数组,返回true
- 取出之前记录的下标
- 如果
remainder不在map中,将其存入map.set(remainder, i)- 注意:如果已经存在,不要更新下标,因为我们要找的是最长的或者说尽早出现的下标,以满足长度 >= 2 的条件
- 如果